# Load in the whole Alzheimers data file
data <- read.csv('~/repos/portfolio/demonstrative/R/datasets/alzheimers/alzheimers.csv')
# Remove this field ... I forgot to do that when creating the dataset
data <- data %>% select(-male)
# Normalize gender labels
stopifnot(all(!is.na(data$gender)))
normalize.gender <- function(x) {
  is.male <- x %>% tolower %>% str_detect('^m')
  ifelse(is.male, 'Male', 'Female') %>% factor
}
data <- data %>% mutate(gender=normalize.gender(gender))
# Convert integer fields to numeric for the sake of consistency
data <- data %>% mutate_each_(funs(as.numeric), c('Betacellulin', 'Eotaxin_3'))
head(data)
X <- data %>% select(-response)
y <- data[,'response']
table(y)
y
   Impaired NotImpaired 
         91         242 
table(y) / length(y)
y
   Impaired NotImpaired 
  0.2732733   0.7267267 
names(X)
  [1] "ACE_CD143_Angiotensin_Converti"   "ACTH_Adrenocorticotropic_Hormon" 
  [3] "AXL"                              "Adiponectin"                     
  [5] "Alpha_1_Antichymotrypsin"         "Alpha_1_Antitrypsin"             
  [7] "Alpha_1_Microglobulin"            "Alpha_2_Macroglobulin"           
  [9] "Angiopoietin_2_ANG_2"             "Angiotensinogen"                 
 [11] "Apolipoprotein_A_IV"              "Apolipoprotein_A1"               
 [13] "Apolipoprotein_A2"                "Apolipoprotein_B"                
 [15] "Apolipoprotein_CI"                "Apolipoprotein_CIII"             
 [17] "Apolipoprotein_D"                 "Apolipoprotein_E"                
 [19] "Apolipoprotein_H"                 "B_Lymphocyte_Chemoattractant_BL" 
 [21] "BMP_6"                            "Beta_2_Microglobulin"            
 [23] "Betacellulin"                     "C_Reactive_Protein"              
 [25] "CD40"                             "CD5L"                            
 [27] "Calbindin"                        "Calcitonin"                      
 [29] "CgA"                              "Clusterin_Apo_J"                 
 [31] "Complement_3"                     "Complement_Factor_H"             
 [33] "Connective_Tissue_Growth_Factor"  "Cortisol"                        
 [35] "Creatine_Kinase_MB"               "Cystatin_C"                      
 [37] "EGF_R"                            "EN_RAGE"                         
 [39] "ENA_78"                           "Eotaxin_3"                       
 [41] "FAS"                              "FSH_Follicle_Stimulation_Hormon" 
 [43] "Fas_Ligand"                       "Fatty_Acid_Binding_Protein"      
 [45] "Ferritin"                         "Fetuin_A"                        
 [47] "Fibrinogen"                       "GRO_alpha"                       
 [49] "Gamma_Interferon_induced_Monokin" "Glutathione_S_Transferase_alpha" 
 [51] "HB_EGF"                           "HCC_4"                           
 [53] "Hepatocyte_Growth_Factor_HGF"     "I_309"                           
 [55] "ICAM_1"                           "IGF_BP_2"                        
 [57] "IL_11"                            "IL_13"                           
 [59] "IL_16"                            "IL_17E"                          
 [61] "IL_1alpha"                        "IL_3"                            
 [63] "IL_4"                             "IL_5"                            
 [65] "IL_6"                             "IL_6_Receptor"                   
 [67] "IL_7"                             "IL_8"                            
 [69] "IP_10_Inducible_Protein_10"       "IgA"                             
 [71] "Insulin"                          "Kidney_Injury_Molecule_1_KIM_1"  
 [73] "LOX_1"                            "Leptin"                          
 [75] "Lipoprotein_a"                    "MCP_1"                           
 [77] "MCP_2"                            "MIF"                             
 [79] "MIP_1alpha"                       "MIP_1beta"                       
 [81] "MMP_2"                            "MMP_3"                           
 [83] "MMP10"                            "MMP7"                            
 [85] "Myoglobin"                        "NT_proBNP"                       
 [87] "NrCAM"                            "Osteopontin"                     
 [89] "PAI_1"                            "PAPP_A"                          
 [91] "PLGF"                             "PYY"                             
 [93] "Pancreatic_polypeptide"           "Prolactin"                       
 [95] "Prostatic_Acid_Phosphatase"       "Protein_S"                       
 [97] "Pulmonary_and_Activation_Regulat" "RANTES"                          
 [99] "Resistin"                         "S100b"                           
[101] "SGOT"                             "SHBG"                            
[103] "SOD"                              "Serum_Amyloid_P"                 
[105] "Sortilin"                         "Stem_Cell_Factor"                
[107] "TGF_alpha"                        "TIMP_1"                          
[109] "TNF_RII"                          "TRAIL_R3"                        
[111] "TTR_prealbumin"                   "Tamm_Horsfall_Protein_THP"       
[113] "Thrombomodulin"                   "Thrombopoietin"                  
[115] "Thymus_Expressed_Chemokine_TECK"  "Thyroid_Stimulating_Hormone"     
[117] "Thyroxine_Binding_Globulin"       "Tissue_Factor"                   
[119] "Transferrin"                      "Trefoil_Factor_3_TFF3"           
[121] "VCAM_1"                           "VEGF"                            
[123] "Vitronectin"                      "von_Willebrand_Factor"           
[125] "age"                              "tau"                             
[127] "p_tau"                            "Ab_42"                           
[129] "Genotype"                         "gender"                          
X %>% sapply(class) %>% table
.
 factor numeric 
      2     128 
#X %>% sapply(class) %>% .[. == 'factor']
names(X)[sapply(X, class) == 'factor']
[1] "Genotype" "gender"  

Partial Analysis

We could start by looking at the relationship between some of the more intuitive variables like Age, Gender, and Genotype and impairment, to see if there are any obvious relationships.

Age vs Impairment

data %>% 
  mutate(age_range=cut(age, breaks=5)) %>%
  group_by(age_range, response) %>% tally %>% 
  plot_ly(x=~age_range, y=~n, color=~response, type='bar') %>%
  plotly::layout(hovermode='closest', title='Age vs Impairment')
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels

Genotype vs Impairment

data %>% 
  group_by(Genotype, response) %>% tally %>% 
  plot_ly(x=~Genotype, y=~n, color=~response, type='bar') %>%
  plotly::layout(hovermode='closest', title='Genotype vs Impairment')
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels

Gender vs Impairment

data %>% 
  group_by(gender, response) %>% tally %>% 
  plot_ly(x=~gender, y=~n, color=~response, type='bar') %>%
  layout(hovermode='closest', title='Gender vs Impairment')
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels

Protein Analysis

Now what about the other ~125 variables? We can’t look at them all one at a time so perhaps there is a way to “condense” them into something more manageable:

library(corrplot)
d.pca <- X %>% select(-gender, -Genotype)
cor.mat <- corrplot(cor(d.pca), order='hclust', tl.col='black', tl.cex=.5)

# Extract the variable names from the figure below since they will be useful
# for creating similar plots for comparison (and it's easier to compare in the same order)
cor.var <- rownames(cor.mat)
#d.pca <- X %>% select(-gender, -Genotype)
pca <- prcomp(d.pca, scale=T)
data.frame(Cumulative.Variance=summary(pca)$importance[3,]) %>% mutate(PC.Number=1:n()) %>%
  plot_ly(x=~PC.Number, y=~Cumulative.Variance, mode='lines', type='scatter') %>%
  layout(title='Cumulative Variance Explained by Principal Components')
corrplot(cor(d.pca[,cor.var], pca$x[,1:25]), tl.col='black', tl.cex=.5)

Regression Models

#registerCores(1)
tr <- proj$getTrainer()
tc <- trainControl(
  classProbs=T, method='cv', number=10,
  summaryFunction = function(...)c(twoClassSummary(...), defaultSummary(...)),
  verboseIter=F, savePredictions='final', returnResamp='final', allowParallel=T
)
get.ensemble.model <- function(tuneList){
  caret.list.args <- list(
    trControl=trainControl(
      method='cv', number=5, classProbs=T, 
      returnData=F, savePredictions='final',
      allowParallel=T, verboseIter=F
    ),
    tuneList=tuneList
  )
  caret.stack.args <- list(
    method=GetEnsembleAveragingModel(),
    trControl=trainControl(method='none', savePredictions = 'final')
  )
  GetCaretEnsembleModel(caret.list.args, caret.stack.args)
}
X.m <- predict(dummyVars(~., X), X) %>% as.data.frame
non.pca.vars <- X.m %>% select(starts_with('gender'), starts_with('Genotype'), starts_with('age')) %>% names
pca.split <- function(X) {
  list(
    var=X %>% dplyr::select(one_of(non.pca.vars)),
    pca=X %>% dplyr::select(-one_of(non.pca.vars))
  )
}
pca.combine <- function(pp, X) {
  X <- pca.split(X)
  cbind(X$var, predict(pp, newdata=X$pca))
}
get.model <- function(model.name){
  model <- getModelInfo()[[model.name]]
  m <- model
  
  m$fit <- function(x, y, wts, param, lev, last, classProbs, ...){
    X <- pca.split(x)
    pp <- preProcess(X$pca, method=c('center', 'scale', 'pca'), pcaComp = 40)
    X.train <- cbind(X$var, predict(pp, newdata=X$pca))
    modelFit <- model$fit(X.train, y, wts, param, lev, last, classProbs, ...)
    modelFit$pp <- pp
    modelFit$feature.names <- names(X.train)
    modelFit
  }
  m$predict <- function (modelFit, newdata, submodels = NULL) {
    X.test <- pca.combine(modelFit$pp, newdata)
    model$predict(modelFit, X.test, submodels)
  }
  m$prob <- function (modelFit, newdata, submodels = NULL){
    X.test <- pca.combine(modelFit$pp, newdata)
    model$prob(modelFit, X.test, submodels)
  }
  if (model.name == 'xgbTree'){
    m$varImp = function(object, numTrees = NULL, ...) {
      imp <- xgb.importance(object$feature.names, model = object)
      imp <- as.data.frame(imp)[, 1:2]
      rownames(imp) <- as.character(imp[,1])
      imp <- imp[,2,drop = FALSE]
      colnames(imp) <- "Overall"
      imp   
    }
  }
  m
}
models <- list(
  tr$getModel('pca_glm', method=get.model('glm'), trControl=tc),
  tr$getModel('pca_glmnet', method=get.model('glmnet'), trControl=tc, tuneLength=5),
  tr$getModel('pca_rf', method=get.model('rf'), trControl=tc, tuneGrid=expand.grid(.mtry = c(2,4,8))),
  tr$getModel('pca_rpart', method=get.model('rpart'), tuneLength=10, trControl=tc),
  tr$getModel('pca_gbm', method=get.model('gbm'), tuneLength=5, trControl=tc, verbose=F),
  tr$getModel('pca_xgb', method=get.model('xgbTree'), tuneLength=5, trControl=tc),
  tr$getModel('pca_spline', method=get.model('earth'), preProcess=pre.proc, trControl=tc, tuneLength=5),
  tr$getModel('pca_nnet', method=get.model('nnet'), preProcess=pre.proc, trControl=tc, tuneLength=5, trace=F)
)
names(models) <- sapply(models, function(m) m$name)
pca.results <- lapply(models, function(m) tr$train(m, X.m, y, enable.cache=T)) %>% setNames(names(models))
2016-11-07 18:36:04 INFO::Beginning training for model "pca_glm" (cache name = "model_pca_glm")
2016-11-07 18:36:04 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_pca_glm.Rdata" from disk
2016-11-07 18:36:04 INFO::Training complete for model "pca_glm"
2016-11-07 18:36:04 INFO::Beginning training for model "pca_glmnet" (cache name = "model_pca_glmnet")
2016-11-07 18:36:04 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_pca_glmnet.Rdata" from disk
2016-11-07 18:36:04 INFO::Training complete for model "pca_glmnet"
2016-11-07 18:36:04 INFO::Beginning training for model "pca_rf" (cache name = "model_pca_rf")
2016-11-07 18:36:04 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_pca_rf.Rdata" from disk
2016-11-07 18:36:04 INFO::Training complete for model "pca_rf"
2016-11-07 18:36:04 INFO::Beginning training for model "pca_rpart" (cache name = "model_pca_rpart")
2016-11-07 18:36:04 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_pca_rpart.Rdata" from disk
2016-11-07 18:36:04 INFO::Training complete for model "pca_rpart"
2016-11-07 18:36:04 INFO::Beginning training for model "pca_gbm" (cache name = "model_pca_gbm")
2016-11-07 18:36:04 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_pca_gbm.Rdata" from disk
2016-11-07 18:36:04 INFO::Training complete for model "pca_gbm"
2016-11-07 18:36:04 INFO::Beginning training for model "pca_xgb" (cache name = "model_pca_xgb")
2016-11-07 18:36:04 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_pca_xgb.Rdata" from disk
2016-11-07 18:36:04 INFO::Training complete for model "pca_xgb"
2016-11-07 18:36:04 INFO::Beginning training for model "pca_spline" (cache name = "model_pca_spline")
2016-11-07 18:36:04 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_pca_spline.Rdata" from disk
2016-11-07 18:36:04 INFO::Training complete for model "pca_spline"
2016-11-07 18:36:04 INFO::Beginning training for model "pca_nnet" (cache name = "model_pca_nnet")
2016-11-07 18:36:04 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_pca_nnet.Rdata" from disk
2016-11-07 18:36:04 INFO::Training complete for model "pca_nnet"
library(caretEnsemble)
pre.proc <- c('center', 'scale')
ens.model <- list(
  glmnet=caretModelSpec(method='glmnet', preProcess=pre.proc, tuneLength=5),
  gbm=caretModelSpec(method='gbm', tuneLength=5, verbose=F),
  spline=caretModelSpec(method='earth', tuneLength=5, preProcess=pre.proc)
)
models <- list(
  tr$getModel('glm', method='glm', preProcess=pre.proc, trControl=tc),
  tr$getModel('glmnet', method='glmnet', preProcess=pre.proc, trControl=tc, tuneLength=5),
  tr$getModel('nnet', method='nnet', preProcess=pre.proc, trControl=tc, tuneLength=5, trace=F),
  tr$getModel('rpart', method='rpart', tuneLength=10, trControl=tc),
  tr$getModel('gbm', method='gbm', tuneLength=5, trControl=tc, verbose=F),
  tr$getModel('xgb', method='xgbTree', tuneLength=5, trControl=tc),
  tr$getModel('spline', method='earth', preProcess=pre.proc, trControl=tc, tuneLength=5),
  tr$getModel('rf', method='rf', trControl=tc, tuneLength=5),
  tr$getModel('ensemble', method=get.ensemble.model(ens.model), trControl=tc, tuneLength=5)
)
names(models) <- sapply(models, function(m) m$name)
all.results <- lapply(models, function(m) tr$train(m, X.m, y, enable.cache=T)) %>% setNames(names(models))
2016-11-07 20:37:59 INFO::Beginning training for model "glm" (cache name = "model_glm")
2016-11-07 20:37:59 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_glm.Rdata" from disk
2016-11-07 20:37:59 INFO::Training complete for model "glm"
2016-11-07 20:37:59 INFO::Beginning training for model "glmnet" (cache name = "model_glmnet")
2016-11-07 20:37:59 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_glmnet.Rdata" from disk
2016-11-07 20:37:59 INFO::Training complete for model "glmnet"
2016-11-07 20:37:59 INFO::Beginning training for model "nnet" (cache name = "model_nnet")
2016-11-07 20:37:59 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_nnet.Rdata" from disk
2016-11-07 20:37:59 INFO::Training complete for model "nnet"
2016-11-07 20:37:59 INFO::Beginning training for model "rpart" (cache name = "model_rpart")
2016-11-07 20:37:59 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_rpart.Rdata" from disk
2016-11-07 20:37:59 INFO::Training complete for model "rpart"
2016-11-07 20:37:59 INFO::Beginning training for model "gbm" (cache name = "model_gbm")
2016-11-07 20:37:59 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_gbm.Rdata" from disk
2016-11-07 20:37:59 INFO::Training complete for model "gbm"
2016-11-07 20:37:59 INFO::Beginning training for model "xgb" (cache name = "model_xgb")
2016-11-07 20:37:59 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_xgb.Rdata" from disk
2016-11-07 20:37:59 INFO::Training complete for model "xgb"
2016-11-07 20:37:59 INFO::Beginning training for model "spline" (cache name = "model_spline")
2016-11-07 20:37:59 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_spline.Rdata" from disk
2016-11-07 20:37:59 INFO::Training complete for model "spline"
2016-11-07 20:37:59 INFO::Beginning training for model "rf" (cache name = "model_rf")
2016-11-07 20:37:59 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_rf.Rdata" from disk
2016-11-07 20:37:59 INFO::Training complete for model "rf"
2016-11-07 20:37:59 INFO::Beginning training for model "ensemble" (cache name = "model_ensemble")
2016-11-07 20:37:59 DEBUG::Restoring cached object "~/data/meetups/r_tutorials/tutorial_03/cache/models/model_ensemble.Rdata" from disk
2016-11-07 20:37:59 INFO::Training complete for model "ensemble"
rbind(GetResampleData(pca.results), GetResampleData(all.results)) %>%
  mutate(model=reorder(model, accuracy, median)) %>%
  plot_ly(x=~model, y=~accuracy, type='box') %>%
  layout(margin=list(b=100))
var.imp <- GetVarImp(all.results)
var.imp %>%
  mutate(feature=reorder(var.imp$feature, var.imp$score, mean)) %>%
  plot_ly(x=~feature, y=~score, color=~model, mode='markers', type='scatter') %>%
  layout(margin=list(b=200))
var.model.names <- names(pca.results)[!str_detect(names(pca.results), 'nnet')]
var.imp <- GetVarImp(pca.results[var.model.names])
var.imp %>%
  mutate(feature=reorder(var.imp$feature, var.imp$score, mean)) %>%
  plot_ly(x=~feature, y=~score, color=~model, mode='markers', type='scatter') %>%
  layout(margin=list(b=200))
pd.vars <- c('tau', 'p_tau', 'Ab_42', 'age', 'gender.Male', 'gender.Female')
pd.models <- c('xgb', 'gbm', 'glmnet', 'spline', 'ensemble')
#pd.models <- c('ensemble')
pred.fun <- function(object, newdata) {
  if ('caretStack' %in% class(object$finalModel)){
    pred <- predict(object$finalModel, newdata=newdata, type='prob')
  } else {
    pred <- predict(object, newdata=newdata, type='prob')
  }
  if (is.vector(pred)) pred
  else pred[,1] 
}
options(error=recover)
#registerCores(1) # Increase this to make PD calcs faster
pd.data <- GetPartialDependence(
  all.results[pd.models], pd.vars, pred.fun, 
  X=X.m, # This can come from model objects but only if returnData=T in trainControl
  grid.size=50, grid.window=c(0, 1), # Resize these to better fit range of data
  sample.rate=1, # Decrease this if PD calculations take too long
  verbose=F, seed=SEED
)
pd.mean <- foreach(pd=pd.data, .combine=rbind)%do%
  { pd$pd %>% dplyr::mutate(predictor=pd$predictor, model=pd$model) } %>%
  dplyr::group_by(predictor, model, x) %>% 
  dplyr::summarise(y.mid=mean(y)) %>% ungroup 
pd.mean %>% ggplot(aes(x=x, y=y.mid, color=model)) + geom_line() + facet_wrap(~predictor, scale='free') +
  theme_bw() + 
  ylab('Predicted Probability') + xlab('Predictor Value')

glmnet.model <- all.results$glmnet$fit$finalModel
glmnet.coef <- predict(glmnet.model, s=all.results$glmnet$fit$bestTune$lambda, type='coefficients')
glmnet.coef <- glmnet.coef[,1]
glmnet.coef %>% data.frame %>% setNames('Coefficient') %>% add_rownames(var='Feature') %>%
  filter(abs(Coefficient) > 0) %>%
  mutate(Feature=reorder(Feature, Coefficient, mean)) %>%
  plot_ly(x=~Feature, y=~Coefficient, type='bar') %>%
  layout(margin=list(b=200), title='Glmnet Coefficients')
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCmBgYHtyIGluaXQsIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nPUYsIG1lc3NhZ2U9RiwgZXJyb3I9RiwgZWNobz1GfQpzb3VyY2UoJ2NvbW1vbi5SJykKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KGtuaXRyKQpsYXlvdXQgPC0gcGxvdGx5OjpsYXlvdXQKCiMgUGFwZXIgb24gZGF0YToKIyBNdWx0aXBsZXhlZCBJbW11bm9hc3NheSBQYW5lbCBJZGVudGlmaWVzIE5vdmVsIENTRiBCaW9tYXJrZXJzIGZvciBBbHpoZWltZXIncyBEaXNlYXNlIERpYWdub3NpcyBhbmQgUHJvZ25vc2lzCiMgbGlicmFyeShBcHBsaWVkUHJlZGljdGl2ZU1vZGVsaW5nKQojID9kaWFnbm9zaXMKYGBgCgpgYGB7cn0KIyBMb2FkIGluIHRoZSB3aG9sZSBBbHpoZWltZXJzIGRhdGEgZmlsZQpkYXRhIDwtIHJlYWQuY3N2KCd+L3JlcG9zL3BvcnRmb2xpby9kZW1vbnN0cmF0aXZlL1IvZGF0YXNldHMvYWx6aGVpbWVycy9hbHpoZWltZXJzLmNzdicpCgojIFJlbW92ZSB0aGlzIGZpZWxkIC4uLiBJIGZvcmdvdCB0byBkbyB0aGF0IHdoZW4gY3JlYXRpbmcgdGhlIGRhdGFzZXQKZGF0YSA8LSBkYXRhICU+JSBzZWxlY3QoLW1hbGUpCgojIE5vcm1hbGl6ZSBnZW5kZXIgbGFiZWxzCnN0b3BpZm5vdChhbGwoIWlzLm5hKGRhdGEkZ2VuZGVyKSkpCm5vcm1hbGl6ZS5nZW5kZXIgPC0gZnVuY3Rpb24oeCkgewogIGlzLm1hbGUgPC0geCAlPiUgdG9sb3dlciAlPiUgc3RyX2RldGVjdCgnXm0nKQogIGlmZWxzZShpcy5tYWxlLCAnTWFsZScsICdGZW1hbGUnKSAlPiUgZmFjdG9yCn0KZGF0YSA8LSBkYXRhICU+JSBtdXRhdGUoZ2VuZGVyPW5vcm1hbGl6ZS5nZW5kZXIoZ2VuZGVyKSkKCiMgQ29udmVydCBpbnRlZ2VyIGZpZWxkcyB0byBudW1lcmljIGZvciB0aGUgc2FrZSBvZiBjb25zaXN0ZW5jeQpkYXRhIDwtIGRhdGEgJT4lIG11dGF0ZV9lYWNoXyhmdW5zKGFzLm51bWVyaWMpLCBjKCdCZXRhY2VsbHVsaW4nLCAnRW90YXhpbl8zJykpCgpoZWFkKGRhdGEpCmBgYAoKCmBgYHtyfQpYIDwtIGRhdGEgJT4lIHNlbGVjdCgtcmVzcG9uc2UpCnkgPC0gZGF0YVssJ3Jlc3BvbnNlJ10KdGFibGUoeSkKYGBgCgpgYGB7cn0KdGFibGUoeSkgLyBsZW5ndGgoeSkKYGBgCgpgYGB7cn0KbmFtZXMoWCkKYGBgCgpgYGB7cn0KIyBCYXNlIFIgdG8gYWNjb21wbGlzaCB0aGUgc2FtZToKIyB0YWJsZShzYXBwbHkoWCwgY2xhc3MpKQoKWCAlPiUgc2FwcGx5KGNsYXNzKSAlPiUgdGFibGUKYGBgCgpgYGB7cn0KIyBQaXBlbGluZSB0byBhY2NvbXBsaXNoIHRoZSBzYW1lOgojIFggJT4lIHNhcHBseShjbGFzcykgJT4lIC5bLiA9PSAnZmFjdG9yJ10KCm5hbWVzKFgpW3NhcHBseShYLCBjbGFzcykgPT0gJ2ZhY3RvciddCmBgYAoKIyBQYXJ0aWFsIEFuYWx5c2lzCgpXZSBjb3VsZCBzdGFydCBieSBsb29raW5nIGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBzb21lIG9mIHRoZSBtb3JlIGludHVpdGl2ZSB2YXJpYWJsZXMgbGlrZSBBZ2UsIEdlbmRlciwgYW5kIEdlbm90eXBlIGFuZCBpbXBhaXJtZW50LCB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBvYnZpb3VzIHJlbGF0aW9uc2hpcHMuCgojIyBBZ2UgdnMgSW1wYWlybWVudAoKYGBge3J9CmRhdGEgJT4lIAogIG11dGF0ZShhZ2VfcmFuZ2U9Y3V0KGFnZSwgYnJlYWtzPTUpKSAlPiUKICBncm91cF9ieShhZ2VfcmFuZ2UsIHJlc3BvbnNlKSAlPiUgdGFsbHkgJT4lIAogIHBsb3RfbHkoeD1+YWdlX3JhbmdlLCB5PX5uLCBjb2xvcj1+cmVzcG9uc2UsIHR5cGU9J2JhcicpICU+JQogIHBsb3RseTo6bGF5b3V0KGhvdmVybW9kZT0nY2xvc2VzdCcsIHRpdGxlPSdBZ2UgdnMgSW1wYWlybWVudCcpCmBgYAoKCiMjIEdlbm90eXBlIHZzIEltcGFpcm1lbnQKCmBgYHtyfQpkYXRhICU+JSAKICBncm91cF9ieShHZW5vdHlwZSwgcmVzcG9uc2UpICU+JSB0YWxseSAlPiUgCiAgcGxvdF9seSh4PX5HZW5vdHlwZSwgeT1+biwgY29sb3I9fnJlc3BvbnNlLCB0eXBlPSdiYXInKSAlPiUKICBwbG90bHk6OmxheW91dChob3Zlcm1vZGU9J2Nsb3Nlc3QnLCB0aXRsZT0nR2Vub3R5cGUgdnMgSW1wYWlybWVudCcpCmBgYAoKCiMjIEdlbmRlciB2cyBJbXBhaXJtZW50CgpgYGB7cn0KZGF0YSAlPiUgCiAgZ3JvdXBfYnkoZ2VuZGVyLCByZXNwb25zZSkgJT4lIHRhbGx5ICU+JSAKICBwbG90X2x5KHg9fmdlbmRlciwgeT1+biwgY29sb3I9fnJlc3BvbnNlLCB0eXBlPSdiYXInKSAlPiUKICBsYXlvdXQoaG92ZXJtb2RlPSdjbG9zZXN0JywgdGl0bGU9J0dlbmRlciB2cyBJbXBhaXJtZW50JykKYGBgCgoKIyMjIFByb3RlaW4gQW5hbHlzaXMKCk5vdyB3aGF0IGFib3V0IHRoZSBvdGhlciB+MTI1IHZhcmlhYmxlcz8gIFdlIGNhbid0IGxvb2sgYXQgdGhlbSBhbGwgb25lIGF0IGEgdGltZSBzbyBwZXJoYXBzIHRoZXJlIGlzIGEgd2F5IHRvICJjb25kZW5zZSIgdGhlbSBpbnRvIHNvbWV0aGluZyBtb3JlIG1hbmFnZWFibGU6CgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0KbGlicmFyeShjb3JycGxvdCkKZC5wY2EgPC0gWCAlPiUgc2VsZWN0KC1nZW5kZXIsIC1HZW5vdHlwZSkKY29yLm1hdCA8LSBjb3JycGxvdChjb3IoZC5wY2EpLCBvcmRlcj0naGNsdXN0JywgdGwuY29sPSdibGFjaycsIHRsLmNleD0uNSkKCiMgRXh0cmFjdCB0aGUgdmFyaWFibGUgbmFtZXMgZnJvbSB0aGUgZmlndXJlIGJlbG93IHNpbmNlIHRoZXkgd2lsbCBiZSB1c2VmdWwKIyBmb3IgY3JlYXRpbmcgc2ltaWxhciBwbG90cyBmb3IgY29tcGFyaXNvbiAoYW5kIGl0J3MgZWFzaWVyIHRvIGNvbXBhcmUgaW4gdGhlIHNhbWUgb3JkZXIpCmNvci52YXIgPC0gcm93bmFtZXMoY29yLm1hdCkKYGBgCgoKYGBge3J9CiNkLnBjYSA8LSBYICU+JSBzZWxlY3QoLWdlbmRlciwgLUdlbm90eXBlKQpwY2EgPC0gcHJjb21wKGQucGNhLCBzY2FsZT1UKQpgYGAKCgoKCmBgYHtyfQpkYXRhLmZyYW1lKEN1bXVsYXRpdmUuVmFyaWFuY2U9c3VtbWFyeShwY2EpJGltcG9ydGFuY2VbMyxdKSAlPiUgbXV0YXRlKFBDLk51bWJlcj0xOm4oKSkgJT4lCiAgcGxvdF9seSh4PX5QQy5OdW1iZXIsIHk9fkN1bXVsYXRpdmUuVmFyaWFuY2UsIG1vZGU9J2xpbmVzJywgdHlwZT0nc2NhdHRlcicpICU+JQogIGxheW91dCh0aXRsZT0nQ3VtdWxhdGl2ZSBWYXJpYW5jZSBFeHBsYWluZWQgYnkgUHJpbmNpcGFsIENvbXBvbmVudHMnKQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9MTB9CmNvcnJwbG90KGNvcihkLnBjYVssY29yLnZhcl0sIHBjYSR4WywxOjI1XSksIHRsLmNvbD0nYmxhY2snLCB0bC5jZXg9LjUpCmBgYAoKCiMgUmVncmVzc2lvbiBNb2RlbHMKCmBgYHtyfQojcmVnaXN0ZXJDb3JlcygxKQoKCnRyIDwtIHByb2okZ2V0VHJhaW5lcigpCnRjIDwtIHRyYWluQ29udHJvbCgKICBjbGFzc1Byb2JzPVQsIG1ldGhvZD0nY3YnLCBudW1iZXI9MTAsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZnVuY3Rpb24oLi4uKWModHdvQ2xhc3NTdW1tYXJ5KC4uLiksIGRlZmF1bHRTdW1tYXJ5KC4uLikpLAogIHZlcmJvc2VJdGVyPUYsIHNhdmVQcmVkaWN0aW9ucz0nZmluYWwnLCByZXR1cm5SZXNhbXA9J2ZpbmFsJywgYWxsb3dQYXJhbGxlbD1UCikKCmdldC5lbnNlbWJsZS5tb2RlbCA8LSBmdW5jdGlvbih0dW5lTGlzdCl7CiAgY2FyZXQubGlzdC5hcmdzIDwtIGxpc3QoCiAgICB0ckNvbnRyb2w9dHJhaW5Db250cm9sKAogICAgICBtZXRob2Q9J2N2JywgbnVtYmVyPTUsIGNsYXNzUHJvYnM9VCwgCiAgICAgIHJldHVybkRhdGE9Riwgc2F2ZVByZWRpY3Rpb25zPSdmaW5hbCcsCiAgICAgIGFsbG93UGFyYWxsZWw9VCwgdmVyYm9zZUl0ZXI9RgogICAgKSwKICAgIHR1bmVMaXN0PXR1bmVMaXN0CiAgKQogIGNhcmV0LnN0YWNrLmFyZ3MgPC0gbGlzdCgKICAgIG1ldGhvZD1HZXRFbnNlbWJsZUF2ZXJhZ2luZ01vZGVsKCksCiAgICB0ckNvbnRyb2w9dHJhaW5Db250cm9sKG1ldGhvZD0nbm9uZScsIHNhdmVQcmVkaWN0aW9ucyA9ICdmaW5hbCcpCiAgKQogIEdldENhcmV0RW5zZW1ibGVNb2RlbChjYXJldC5saXN0LmFyZ3MsIGNhcmV0LnN0YWNrLmFyZ3MpCn0KClgubSA8LSBwcmVkaWN0KGR1bW15VmFycyh+LiwgWCksIFgpICU+JSBhcy5kYXRhLmZyYW1lCgpub24ucGNhLnZhcnMgPC0gWC5tICU+JSBzZWxlY3Qoc3RhcnRzX3dpdGgoJ2dlbmRlcicpLCBzdGFydHNfd2l0aCgnR2Vub3R5cGUnKSwgc3RhcnRzX3dpdGgoJ2FnZScpKSAlPiUgbmFtZXMKCnBjYS5zcGxpdCA8LSBmdW5jdGlvbihYKSB7CiAgbGlzdCgKICAgIHZhcj1YICU+JSBkcGx5cjo6c2VsZWN0KG9uZV9vZihub24ucGNhLnZhcnMpKSwKICAgIHBjYT1YICU+JSBkcGx5cjo6c2VsZWN0KC1vbmVfb2Yobm9uLnBjYS52YXJzKSkKICApCn0KCnBjYS5jb21iaW5lIDwtIGZ1bmN0aW9uKHBwLCBYKSB7CiAgWCA8LSBwY2Euc3BsaXQoWCkKICBjYmluZChYJHZhciwgcHJlZGljdChwcCwgbmV3ZGF0YT1YJHBjYSkpCn0KCmdldC5tb2RlbCA8LSBmdW5jdGlvbihtb2RlbC5uYW1lKXsKICBtb2RlbCA8LSBnZXRNb2RlbEluZm8oKVtbbW9kZWwubmFtZV1dCiAgbSA8LSBtb2RlbAogIAogIG0kZml0IDwtIGZ1bmN0aW9uKHgsIHksIHd0cywgcGFyYW0sIGxldiwgbGFzdCwgY2xhc3NQcm9icywgLi4uKXsKICAgIFggPC0gcGNhLnNwbGl0KHgpCiAgICBwcCA8LSBwcmVQcm9jZXNzKFgkcGNhLCBtZXRob2Q9YygnY2VudGVyJywgJ3NjYWxlJywgJ3BjYScpLCBwY2FDb21wID0gNDApCiAgICBYLnRyYWluIDwtIGNiaW5kKFgkdmFyLCBwcmVkaWN0KHBwLCBuZXdkYXRhPVgkcGNhKSkKICAgIG1vZGVsRml0IDwtIG1vZGVsJGZpdChYLnRyYWluLCB5LCB3dHMsIHBhcmFtLCBsZXYsIGxhc3QsIGNsYXNzUHJvYnMsIC4uLikKICAgIG1vZGVsRml0JHBwIDwtIHBwCiAgICBtb2RlbEZpdCRmZWF0dXJlLm5hbWVzIDwtIG5hbWVzKFgudHJhaW4pCiAgICBtb2RlbEZpdAogIH0KICBtJHByZWRpY3QgPC0gZnVuY3Rpb24gKG1vZGVsRml0LCBuZXdkYXRhLCBzdWJtb2RlbHMgPSBOVUxMKSB7CiAgICBYLnRlc3QgPC0gcGNhLmNvbWJpbmUobW9kZWxGaXQkcHAsIG5ld2RhdGEpCiAgICBtb2RlbCRwcmVkaWN0KG1vZGVsRml0LCBYLnRlc3QsIHN1Ym1vZGVscykKICB9CiAgbSRwcm9iIDwtIGZ1bmN0aW9uIChtb2RlbEZpdCwgbmV3ZGF0YSwgc3VibW9kZWxzID0gTlVMTCl7CiAgICBYLnRlc3QgPC0gcGNhLmNvbWJpbmUobW9kZWxGaXQkcHAsIG5ld2RhdGEpCiAgICBtb2RlbCRwcm9iKG1vZGVsRml0LCBYLnRlc3QsIHN1Ym1vZGVscykKICB9CiAgaWYgKG1vZGVsLm5hbWUgPT0gJ3hnYlRyZWUnKXsKICAgIG0kdmFySW1wID0gZnVuY3Rpb24ob2JqZWN0LCBudW1UcmVlcyA9IE5VTEwsIC4uLikgewogICAgICBpbXAgPC0geGdiLmltcG9ydGFuY2Uob2JqZWN0JGZlYXR1cmUubmFtZXMsIG1vZGVsID0gb2JqZWN0KQogICAgICBpbXAgPC0gYXMuZGF0YS5mcmFtZShpbXApWywgMToyXQogICAgICByb3duYW1lcyhpbXApIDwtIGFzLmNoYXJhY3RlcihpbXBbLDFdKQogICAgICBpbXAgPC0gaW1wWywyLGRyb3AgPSBGQUxTRV0KICAgICAgY29sbmFtZXMoaW1wKSA8LSAiT3ZlcmFsbCIKICAgICAgaW1wICAgCiAgICB9CiAgfQogIG0KfQpgYGAKCgpgYGB7cn0KbW9kZWxzIDwtIGxpc3QoCiAgdHIkZ2V0TW9kZWwoJ3BjYV9nbG0nLCBtZXRob2Q9Z2V0Lm1vZGVsKCdnbG0nKSwgdHJDb250cm9sPXRjKSwKICB0ciRnZXRNb2RlbCgncGNhX2dsbW5ldCcsIG1ldGhvZD1nZXQubW9kZWwoJ2dsbW5ldCcpLCB0ckNvbnRyb2w9dGMsIHR1bmVMZW5ndGg9NSksCiAgdHIkZ2V0TW9kZWwoJ3BjYV9yZicsIG1ldGhvZD1nZXQubW9kZWwoJ3JmJyksIHRyQ29udHJvbD10YywgdHVuZUdyaWQ9ZXhwYW5kLmdyaWQoLm10cnkgPSBjKDIsNCw4KSkpLAogIHRyJGdldE1vZGVsKCdwY2FfcnBhcnQnLCBtZXRob2Q9Z2V0Lm1vZGVsKCdycGFydCcpLCB0dW5lTGVuZ3RoPTEwLCB0ckNvbnRyb2w9dGMpLAogIHRyJGdldE1vZGVsKCdwY2FfZ2JtJywgbWV0aG9kPWdldC5tb2RlbCgnZ2JtJyksIHR1bmVMZW5ndGg9NSwgdHJDb250cm9sPXRjLCB2ZXJib3NlPUYpLAogIHRyJGdldE1vZGVsKCdwY2FfeGdiJywgbWV0aG9kPWdldC5tb2RlbCgneGdiVHJlZScpLCB0dW5lTGVuZ3RoPTUsIHRyQ29udHJvbD10YyksCiAgdHIkZ2V0TW9kZWwoJ3BjYV9zcGxpbmUnLCBtZXRob2Q9Z2V0Lm1vZGVsKCdlYXJ0aCcpLCBwcmVQcm9jZXNzPXByZS5wcm9jLCB0ckNvbnRyb2w9dGMsIHR1bmVMZW5ndGg9NSksCiAgdHIkZ2V0TW9kZWwoJ3BjYV9ubmV0JywgbWV0aG9kPWdldC5tb2RlbCgnbm5ldCcpLCBwcmVQcm9jZXNzPXByZS5wcm9jLCB0ckNvbnRyb2w9dGMsIHR1bmVMZW5ndGg9NSwgdHJhY2U9RikKKQpuYW1lcyhtb2RlbHMpIDwtIHNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKG0pIG0kbmFtZSkKCnBjYS5yZXN1bHRzIDwtIGxhcHBseShtb2RlbHMsIGZ1bmN0aW9uKG0pIHRyJHRyYWluKG0sIFgubSwgeSwgZW5hYmxlLmNhY2hlPVQpKSAlPiUgc2V0TmFtZXMobmFtZXMobW9kZWxzKSkKYGBgCgpgYGB7cn0KbGlicmFyeShjYXJldEVuc2VtYmxlKQpwcmUucHJvYyA8LSBjKCdjZW50ZXInLCAnc2NhbGUnKQoKZW5zLm1vZGVsIDwtIGxpc3QoCiAgZ2xtbmV0PWNhcmV0TW9kZWxTcGVjKG1ldGhvZD0nZ2xtbmV0JywgcHJlUHJvY2Vzcz1wcmUucHJvYywgdHVuZUxlbmd0aD01KSwKICBnYm09Y2FyZXRNb2RlbFNwZWMobWV0aG9kPSdnYm0nLCB0dW5lTGVuZ3RoPTUsIHZlcmJvc2U9RiksCiAgc3BsaW5lPWNhcmV0TW9kZWxTcGVjKG1ldGhvZD0nZWFydGgnLCB0dW5lTGVuZ3RoPTUsIHByZVByb2Nlc3M9cHJlLnByb2MpCikKbW9kZWxzIDwtIGxpc3QoCiAgdHIkZ2V0TW9kZWwoJ2dsbScsIG1ldGhvZD0nZ2xtJywgcHJlUHJvY2Vzcz1wcmUucHJvYywgdHJDb250cm9sPXRjKSwKICB0ciRnZXRNb2RlbCgnZ2xtbmV0JywgbWV0aG9kPSdnbG1uZXQnLCBwcmVQcm9jZXNzPXByZS5wcm9jLCB0ckNvbnRyb2w9dGMsIHR1bmVMZW5ndGg9NSksCiAgdHIkZ2V0TW9kZWwoJ25uZXQnLCBtZXRob2Q9J25uZXQnLCBwcmVQcm9jZXNzPXByZS5wcm9jLCB0ckNvbnRyb2w9dGMsIHR1bmVMZW5ndGg9NSwgdHJhY2U9RiksCiAgdHIkZ2V0TW9kZWwoJ3JwYXJ0JywgbWV0aG9kPSdycGFydCcsIHR1bmVMZW5ndGg9MTAsIHRyQ29udHJvbD10YyksCiAgdHIkZ2V0TW9kZWwoJ2dibScsIG1ldGhvZD0nZ2JtJywgdHVuZUxlbmd0aD01LCB0ckNvbnRyb2w9dGMsIHZlcmJvc2U9RiksCiAgdHIkZ2V0TW9kZWwoJ3hnYicsIG1ldGhvZD0neGdiVHJlZScsIHR1bmVMZW5ndGg9NSwgdHJDb250cm9sPXRjKSwKICB0ciRnZXRNb2RlbCgnc3BsaW5lJywgbWV0aG9kPSdlYXJ0aCcsIHByZVByb2Nlc3M9cHJlLnByb2MsIHRyQ29udHJvbD10YywgdHVuZUxlbmd0aD01KSwKICB0ciRnZXRNb2RlbCgncmYnLCBtZXRob2Q9J3JmJywgdHJDb250cm9sPXRjLCB0dW5lTGVuZ3RoPTUpLAogIHRyJGdldE1vZGVsKCdlbnNlbWJsZScsIG1ldGhvZD1nZXQuZW5zZW1ibGUubW9kZWwoZW5zLm1vZGVsKSwgdHJDb250cm9sPXRjLCB0dW5lTGVuZ3RoPTUpCikKbmFtZXMobW9kZWxzKSA8LSBzYXBwbHkobW9kZWxzLCBmdW5jdGlvbihtKSBtJG5hbWUpCgphbGwucmVzdWx0cyA8LSBsYXBwbHkobW9kZWxzLCBmdW5jdGlvbihtKSB0ciR0cmFpbihtLCBYLm0sIHksIGVuYWJsZS5jYWNoZT1UKSkgJT4lIHNldE5hbWVzKG5hbWVzKG1vZGVscykpCmBgYAoKCmBgYHtyfQpyYmluZChHZXRSZXNhbXBsZURhdGEocGNhLnJlc3VsdHMpLCBHZXRSZXNhbXBsZURhdGEoYWxsLnJlc3VsdHMpKSAlPiUKICBtdXRhdGUobW9kZWw9cmVvcmRlcihtb2RlbCwgYWNjdXJhY3ksIG1lZGlhbikpICU+JQogIHBsb3RfbHkoeD1+bW9kZWwsIHk9fmFjY3VyYWN5LCB0eXBlPSdib3gnKSAlPiUKICBsYXlvdXQobWFyZ2luPWxpc3QoYj0xMDApKQpgYGAKCgpgYGB7cn0KdmFyLmltcCA8LSBHZXRWYXJJbXAoYWxsLnJlc3VsdHMpCnZhci5pbXAgJT4lCiAgbXV0YXRlKGZlYXR1cmU9cmVvcmRlcih2YXIuaW1wJGZlYXR1cmUsIHZhci5pbXAkc2NvcmUsIG1lYW4pKSAlPiUKICBwbG90X2x5KHg9fmZlYXR1cmUsIHk9fnNjb3JlLCBjb2xvcj1+bW9kZWwsIG1vZGU9J21hcmtlcnMnLCB0eXBlPSdzY2F0dGVyJykgJT4lCiAgbGF5b3V0KG1hcmdpbj1saXN0KGI9MjAwKSkKYGBgCgoKCmBgYHtyfQp2YXIubW9kZWwubmFtZXMgPC0gbmFtZXMocGNhLnJlc3VsdHMpWyFzdHJfZGV0ZWN0KG5hbWVzKHBjYS5yZXN1bHRzKSwgJ25uZXQnKV0KdmFyLmltcCA8LSBHZXRWYXJJbXAocGNhLnJlc3VsdHNbdmFyLm1vZGVsLm5hbWVzXSkKdmFyLmltcCAlPiUKICBtdXRhdGUoZmVhdHVyZT1yZW9yZGVyKHZhci5pbXAkZmVhdHVyZSwgdmFyLmltcCRzY29yZSwgbWVhbikpICU+JQogIHBsb3RfbHkoeD1+ZmVhdHVyZSwgeT1+c2NvcmUsIGNvbG9yPX5tb2RlbCwgbW9kZT0nbWFya2VycycsIHR5cGU9J3NjYXR0ZXInKSAlPiUKICBsYXlvdXQobWFyZ2luPWxpc3QoYj0yMDApKQpgYGAKCgo8IS0tIHtyfSAtLT4KPCEtLSAjcGMzIDwtIHJlc3VsdHMkcGNhX3hnYiRmaXQkZmluYWxNb2RlbCRwcCRyb3RhdGlvblssM10gIC0tPgo8IS0tIHNjYWxlX3ZlYyA8LSBmdW5jdGlvbih4KSAoeCAtIG1lZGlhbih4KSkgLyBJUVIoeCkgLS0+CjwhLS0gcmVzdWx0cyRwY2FfeGdiJGZpdCRmaW5hbE1vZGVsJHBwJHJvdGF0aW9uICU+JSB0ICU+JSBhcHBseSgyLCBzY2FsZV92ZWMpICU+JSAgLS0+CjwhLS0gICBhcy5kYXRhLmZyYW1lICU+JSBhZGRfcm93bmFtZXModmFyPSdQQycpICU+JSAgLS0+CjwhLS0gICBwbG90X2x5KHg9fnRhdSwgeT1+QWJfNDIsIHRleHQ9flBDLCB0eXBlPSdzY2F0dGVyJywgbW9kZT0nbWFya2VycycpIC0tPgoKCmBgYHtyfQoKcGQudmFycyA8LSBjKCd0YXUnLCAncF90YXUnLCAnQWJfNDInLCAnYWdlJywgJ2dlbmRlci5NYWxlJywgJ2dlbmRlci5GZW1hbGUnKQpwZC5tb2RlbHMgPC0gYygneGdiJywgJ2dibScsICdnbG1uZXQnLCAnc3BsaW5lJywgJ2Vuc2VtYmxlJykKI3BkLm1vZGVscyA8LSBjKCdlbnNlbWJsZScpCgpwcmVkLmZ1biA8LSBmdW5jdGlvbihvYmplY3QsIG5ld2RhdGEpIHsKICBpZiAoJ2NhcmV0U3RhY2snICVpbiUgY2xhc3Mob2JqZWN0JGZpbmFsTW9kZWwpKXsKICAgIHByZWQgPC0gcHJlZGljdChvYmplY3QkZmluYWxNb2RlbCwgbmV3ZGF0YT1uZXdkYXRhLCB0eXBlPSdwcm9iJykKICB9IGVsc2UgewogICAgcHJlZCA8LSBwcmVkaWN0KG9iamVjdCwgbmV3ZGF0YT1uZXdkYXRhLCB0eXBlPSdwcm9iJykKICB9CiAgaWYgKGlzLnZlY3RvcihwcmVkKSkgcHJlZAogIGVsc2UgcHJlZFssMV0gCn0Kb3B0aW9ucyhlcnJvcj1yZWNvdmVyKQojcmVnaXN0ZXJDb3JlcygxKSAjIEluY3JlYXNlIHRoaXMgdG8gbWFrZSBQRCBjYWxjcyBmYXN0ZXIKcGQuZGF0YSA8LSBHZXRQYXJ0aWFsRGVwZW5kZW5jZSgKICBhbGwucmVzdWx0c1twZC5tb2RlbHNdLCBwZC52YXJzLCBwcmVkLmZ1biwgCiAgWD1YLm0sICMgVGhpcyBjYW4gY29tZSBmcm9tIG1vZGVsIG9iamVjdHMgYnV0IG9ubHkgaWYgcmV0dXJuRGF0YT1UIGluIHRyYWluQ29udHJvbAogIGdyaWQuc2l6ZT01MCwgZ3JpZC53aW5kb3c9YygwLCAxKSwgIyBSZXNpemUgdGhlc2UgdG8gYmV0dGVyIGZpdCByYW5nZSBvZiBkYXRhCiAgc2FtcGxlLnJhdGU9MSwgIyBEZWNyZWFzZSB0aGlzIGlmIFBEIGNhbGN1bGF0aW9ucyB0YWtlIHRvbyBsb25nCiAgdmVyYm9zZT1GLCBzZWVkPVNFRUQKKQoKYGBgCgoKYGBge3IsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTN9CnBkLm1lYW4gPC0gZm9yZWFjaChwZD1wZC5kYXRhLCAuY29tYmluZT1yYmluZCklZG8lCiAgeyBwZCRwZCAlPiUgZHBseXI6Om11dGF0ZShwcmVkaWN0b3I9cGQkcHJlZGljdG9yLCBtb2RlbD1wZCRtb2RlbCkgfSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkocHJlZGljdG9yLCBtb2RlbCwgeCkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UoeS5taWQ9bWVhbih5KSkgJT4lIHVuZ3JvdXAgCgpwZC5tZWFuICU+JSBnZ3Bsb3QoYWVzKHg9eCwgeT15Lm1pZCwgY29sb3I9bW9kZWwpKSArIGdlb21fbGluZSgpICsgZmFjZXRfd3JhcCh+cHJlZGljdG9yLCBzY2FsZT0nZnJlZScpICsKICB0aGVtZV9idygpICsgCiAgeWxhYignUHJlZGljdGVkIFByb2JhYmlsaXR5JykgKyB4bGFiKCdQcmVkaWN0b3IgVmFsdWUnKQpgYGAKCgpgYGB7cn0KZ2xtbmV0Lm1vZGVsIDwtIGFsbC5yZXN1bHRzJGdsbW5ldCRmaXQkZmluYWxNb2RlbApnbG1uZXQuY29lZiA8LSBwcmVkaWN0KGdsbW5ldC5tb2RlbCwgcz1hbGwucmVzdWx0cyRnbG1uZXQkZml0JGJlc3RUdW5lJGxhbWJkYSwgdHlwZT0nY29lZmZpY2llbnRzJykKZ2xtbmV0LmNvZWYgPC0gZ2xtbmV0LmNvZWZbLDFdCmdsbW5ldC5jb2VmICU+JSBkYXRhLmZyYW1lICU+JSBzZXROYW1lcygnQ29lZmZpY2llbnQnKSAlPiUgYWRkX3Jvd25hbWVzKHZhcj0nRmVhdHVyZScpICU+JQogIGZpbHRlcihhYnMoQ29lZmZpY2llbnQpID4gMCkgJT4lCiAgbXV0YXRlKEZlYXR1cmU9cmVvcmRlcihGZWF0dXJlLCBDb2VmZmljaWVudCwgbWVhbikpICU+JQogIHBsb3RfbHkoeD1+RmVhdHVyZSwgeT1+Q29lZmZpY2llbnQsIHR5cGU9J2JhcicpICU+JQogIGxheW91dChtYXJnaW49bGlzdChiPTIwMCksIHRpdGxlPSdHbG1uZXQgQ29lZmZpY2llbnRzJykKYGBgCgo8IS0tIEdyYXZleWFyZCAtLT4KCgo8IS0tICMgVGhlIGNvZGUgYmVsb3cgd2lsbCBwbG90IHRoZSBQQ0EgbG9hZGluZ3MgKGllIHBjYSRyb3RhdGlvbikgbWF0cml4IC0tPgo8IS0tICMgZGlyZWN0bHkgcmF0aGVyIHRoYW4gbG9va2luZyBhdCBjb3JyZWxhdGlvbnMgYmV0d2VlbiBvcmlnaW5hbCBhbmQgdHJhbnNmb3JtZWQgdmFyaWFibGVzIC0tPgo8IS0tICMgKHRob3VnaCB0aGVzZSBzaG93IGFib3V0IHRoZSBzYW1lIHRoaW5nKSAtLT4KCjwhLS0Ke3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD02fQpsaWJyYXJ5KHJlc2hhcGUyKQpkcCA8LSBwY2Ekcm90YXRpb25bLDE6MjVdW2Nvci52YXIsXSAlPiUgYXMuZGF0YS5mcmFtZSAlPiUKICBhZGRfcm93bmFtZXModmFyPSdmZWF0dXJlJykgJT4lCiAgbWVsdChpZC52YXJzPSdmZWF0dXJlJywgdmFyaWFibGUubmFtZT0ncGMnKQpkcCRmZWF0dXJlIDwtIGZhY3RvcihkcCRmZWF0dXJlLCBsZXZlbHM9cmV2KGNvci52YXIpKQpkcCAlPiUgZ2dwbG90KGFlcyh4PXBjLCB5PWZlYXR1cmUsIGZpbGw9dmFsdWUpKSArIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3c9J3JlZCcsIGhpZ2g9J2JsdWUnLCBtaWQ9J3doaXRlJykKLS0+Cgo8IS0tIFBDQSArIERhdGEgUHJvamVjdGlvbnMgLS0+CjwhLS0ge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0gLS0+CjwhLS0gaS5wY2EgPC0gYygxLDIpIC0tPgo8IS0tIGQucGNhLnByZWQgPC0gYXMuZGF0YS5mcmFtZShwcmVkaWN0KHBjYSwgZC5wY2EpWyxpLnBjYV0pICU+JSBzZXROYW1lcyguLCBjKCdQQzEnLCAnUEMyJykpIC0tPgo8IS0tIGQucGNhLnByZWQkcmVzcG9uc2UgPC0geSAtLT4KCjwhLS0gIyBkLnBjYS5wcmVkICU+JSBwbG90X2x5KHg9flBDMSwgeT1+UEMyLCBjb2xvcj1+cmVzcG9uc2UsIHR5cGU9J3NjYXR0ZXInLCBtb2RlPSdtYXJrZXJzJykgLS0+Cgo8IS0tICMgUGFyYW1ldGVycyBmb3IgYXhpcyB3aXRoIG5vIGdyaWQgbGluZXMsIHRpY2tzIG9yIGxhYmVscyAtLT4KPCEtLSBlbXB0eS5heGlzIDwtIGxpc3QoIC0tPgo8IS0tICAgdGl0bGUgPSAnJywgLS0+CjwhLS0gICB6ZXJvbGluZSA9IEZBTFNFLCAtLT4KPCEtLSAgIHNob3dsaW5lID0gRkFMU0UsIC0tPgo8IS0tICAgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSwgLS0+CjwhLS0gICB0aWNrbGVuID0gMCwgLS0+CjwhLS0gICBzaG93Z3JpZCA9IEZBTFNFIC0tPgo8IS0tICkgLS0+Cgo8IS0tICMgQ3JlYXRlIGEgbGluZSBwbG90IG9mIGVhY2ggdmFyaWFibGUgc2hvd2luZyB3aGljaCBkaXJlY3Rpb24gaXQgbW92ZXMgd2l0aGluIG91ciAyRCBzcGFjZSAtLT4KPCEtLSBwMSA8LSBwbG90X2x5KCAtLT4KPCEtLSAgICAgZC5wY2EucHJlZCwgeD1+UEMyLCB5PX5QQzEsIHR5cGU9J3NjYXR0ZXInLCBjb2xvcj1+cmVzcG9uc2UsICAtLT4KPCEtLSAgICAgbW9kZT0nbWFya2VycycsIG9wYWNpdHk9MSAtLT4KPCEtLSAgICkgJT4lICAtLT4KPCEtLSAgIGxheW91dCggLS0+CjwhLS0gICAgIHhheGlzPWxpc3Qoc2hvd2dyaWQ9RiwgemVyb2xpbmU9VCksIC0tPgo8IS0tICAgICB5YXhpcz1saXN0KHNob3dncmlkPUYsIHplcm9saW5lPVQpIC0tPgo8IS0tICAgKSAtLT4KCjwhLS0gIyBDcmVhdGUgYSBoZWF0bWFwIG9mIGltcGFpcm1lbnQgaW5jaWRlbmNlIHJhdGUgYWNyb3NzIG91ciAyRCBzcGFjZSAtLT4KPCEtLSBkLmhtIDwtIGQucGNhLnByZWQgJT4lICAtLT4KPCEtLSAgIG11dGF0ZShQQzE9YXMuY2hhcmFjdGVyKGN1dChQQzEsIGJyZWFrcz0zKSksIFBDMj1hcy5jaGFyYWN0ZXIoY3V0KFBDMiwgYnJlYWtzPTMpKSkgJT4lIC0tPgo8IS0tICAgZ3JvdXBfYnkoUEMxLCBQQzIpICU+JSBzdW1tYXJpc2UoUENUPTEwMCpzdW0ocmVzcG9uc2UgPT0gJ0ltcGFpcmVkJykvbigpKSAlPiUgLS0+CjwhLS0gICBhY2FzdChQQzEgfiBQQzIsIHZhbHVlLnZhcj0nUENUJykgLS0+CjwhLS0gcDIgPC0gcGxvdF9seSh6PWQuaG1bYygzLDIsMSksXSwgdHlwZT0naGVhdG1hcCcsIHJldmVyc2VzY2FsZT1GKSAjJT4lIC0tPgo8IS0tICAgI2xheW91dCh4YXhpcz1lbXB0eS5heGlzLCB5YXhpcz1lbXB0eS5heGlzKSAtLT4KCjwhLS0gIyBPdmVybGF5IHRoZSBhYm92ZSBwbG90cyBvbiB0b3Agb2Ygb25lIGFub3RoZXIgLS0+CjwhLS0gc3VicGxvdChwMiwgcDEsIG1hcmdpbj0tMSkgJT4lICAtLT4KPCEtLSAgIGxheW91dCggLS0+CjwhLS0gICAgIHBhcGVyX2JnY29sb3I9J3JnYmEoMCwwLDAsMCknLCBwbG90X2JnY29sb3I9J3JnYmEoMCwwLDAsMCknLCAgLS0+CjwhLS0gICAgIHdpZHRoPTc1MCwgaGVpZ2h0PTUwMCwgLS0+CjwhLS0gICAgIHRpdGxlPScyRCBQcm9qZWN0aW9uIG9mIENvcnJlbGF0ZWQgRmVhdHVyZXMgT3ZlcmxheWVkIHcvIEltcGFpcm1lbnQgUmF0ZXMnIC0tPgo8IS0tICAgKSAtLT4KCgoKPCEtLSBUU05FIHByb2plY3Rpb25zIC0tPgo8IS0tIHtyfSAtLT4KPCEtLSBsaWJyYXJ5KHRzbmUpIC0tPgo8IS0tIGQudHNuZSA8LSBYICU+JSBzZWxlY3QoLWdlbmRlciwgLUdlbm90eXBlKSAtLT4KPCEtLSBzY2FsZV92ZWMgPC0gZnVuY3Rpb24oeCkgKHggLSBtZWFuKHgpKSAvIHNkKHgpIC0tPgo8IS0tIGQudHNuZSA8LSBkLnRzbmUgJT4lIG11dGF0ZV9lYWNoKGZ1bnMoc2NhbGVfdmVjKSkgLS0+CjwhLS0gbS50c25lIDwtIHRzbmUoZC50c25lKSAtLT4KPCEtLSBtLnRzbmUgJT4lIGFzLmRhdGEuZnJhbWUgJT4lIGdncGxvdChhZXMoeD1WMSwgeT1WMikpICsgZ2VvbV9wb2ludCgpIC0tPgoK